/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.file;

import java.io.File;

import cn.seaboot.commons.lang.P;
import cn.seaboot.commons.lang.Symbol;

/**
 * 代码参考自 Apache common-io
 * 主要用于处理扩展名和mime类型
 *
 * @author Mr.css
 * @version 2022-03-16 9:48
 */
public class FilenameUtils {

    /**
     * 无效的文件名字符
     */
    public static final String reservedChars = "\\/<>:\"|?*";

    /**
     * 获取文件名
     *
     * @param filename 文件路径
     * @return 文件名
     */
    public static String getName(String filename) {
        if (filename == null) {
            return null;
        } else {
            int index = indexOfLastSeparator(filename);
            return filename.substring(index + 1);
        }
    }


    /**
     * Gets the base name, minus the full path and extension, from a full filename.
     * <p>
     * This method will handle a file in either Unix or Windows format.
     * The text after the last forward or backslash and before the last dot is returned.
     * <pre>
     * a/b/c.txt --> c
     * a.txt     --> a
     * a/b/c     --> c
     * a/b/c/    --> ""
     * </pre>
     * <p>
     * The output will be the same irrespective of the machine that the code is running on.
     *
     * @param filename the filename to query, null returns null
     * @return the name of the file without the path, or an empty string if none exists
     */
    public static String getBaseName(String filename) {
        return removeExtension(getName(filename));
    }


    /**
     * Removes the extension from a filename.
     * <p>
     * This method returns the textual part of the filename before the last dot.
     * There must be no directory separator after the dot.
     * <pre>
     * foo.txt    --> foo
     * a\b\c.jpg  --> a\b\c
     * a\b\c      --> a\b\c
     * a.b\c      --> a.b\c
     * </pre>
     * <p>
     * The output will be the same irrespective of the machine that the code is running on.
     *
     * @param filename the filename to query, null returns null
     * @return the filename minus the extension
     */
    public static String removeExtension(String filename) {
        if (filename == null) {
            return null;
        }
        int index = indexOfExtension(filename);
        if (index == -1) {
            return filename;
        } else {
            return filename.substring(0, index);
        }
    }

    /**
     * Gets the extension of a filename.
     * <p>
     * This method returns the textual part of the filename after the last dot.
     * There must be no directory separator after the dot.
     * <pre>
     * foo.txt      --> "txt"
     * a/b/c.jpg    --> "jpg"
     * a/b.txt/c    --> ""
     * a/b/c        --> ""
     * </pre>
     * <p>
     * The output will be the same irrespective of the machine that the code is running on.
     *
     * @param filename the filename to retrieve the extension of.
     * @return the extension of the file or an empty string if none exists or <code>null</code>
     * if the filename is <code>null</code>.
     */
    public static String getExtension(String filename) {
        if (filename == null) {
            return null;
        }
        int index = indexOfExtension(filename);
        if (index == -1) {
            return "";
        } else {
            return filename.substring(index + 1);
        }
    }


    /**
     * Gets the extension of a filename.
     * <p>
     * This method returns the textual part of the filename after the last dot.
     * There must be no directory separator after the dot.
     * <pre>
     * foo.txt      --> "txt"
     * a/b/c.jpg    --> "jpg"
     * a/b.txt/c    --> ""
     * a/b/c        --> ""
     * </pre>
     * <p>
     * The output will be the same irrespective of the machine that the code is running on.
     *
     * @param file the file to retrieve the extension of.
     * @return the extension of the file or an empty string if none exists or <code>null</code>
     * if the filename is <code>null</code>.
     */
    public static String getExtension(File file) {
        return getExtension(file.getName());
    }


    /**
     * Returns the index of the last extension separator character, which is a dot.
     * <p>
     * This method also checks that there is no directory separator after the last dot.
     * To do this it uses {@link #indexOfLastSeparator(String)} which will
     * handle a file in either Unix or Windows format.
     * <p>
     * The output will be the same irrespective of the machine that the code is running on.
     *
     * @param filename the filename to find the last path separator in, null returns -1
     * @return the index of the last separator character, or -1 if there
     * is no such character
     */
    public static int indexOfExtension(String filename) {
        if (filename == null) {
            return -1;
        }
        int extensionPos = filename.lastIndexOf(Symbol.PERIOD);
        int lastSeparator = indexOfLastSeparator(filename);
        return lastSeparator > extensionPos ? -1 : extensionPos;
    }

    /**
     * Returns the index of the last directory separator character.
     * <p>
     * This method will handle a file in either Unix or Windows format.
     * The position of the last forward or backslash is returned.
     * <p>
     * The output will be the same irrespective of the machine that the code is running on.
     *
     * @param filename the filename to find the last path separator in, null returns -1
     * @return the index of the last separator character, or -1 if there
     * is no such character
     */
    public static int indexOfLastSeparator(String filename) {
        if (filename == null) {
            return -1;
        }
        int lastUnixPos = filename.lastIndexOf(P.UNIX_SEPARATOR);
        int lastWindowsPos = filename.lastIndexOf(P.WINDOWS_SEPARATOR);
        return Math.max(lastUnixPos, lastWindowsPos);
    }


    /**
     * 检测路径中 './'
     * <p>
     * 对于需要配置的地址路径，需要严格限制，不允许访问到系统其它位置
     *
     * @param path 相对路径
     * @return if valid
     */
    public static boolean isValidFilePath(String path) {
        if (path != null) {
            char ch;
            for (int i = 0, len = path.length(); i < len; i++) {
                ch = path.charAt(i);
                if (ch == '.') {
                    int next = i + 1;
                    if (next < len && path.charAt(next) == '/') {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 检查文件路径是否合法
     * <p>
     * 注意：除了非法字符，还会检查 './'，防止夸目录访问；
     *
     * @param path    相对路径
     * @param message 错误信息
     * @throws IllegalArgumentException 参数验证无效
     */
    public static void validFilePath(String path, String message) {
        if (!isValidFilePath(path)) {
            throw new IllegalArgumentException(message);
        }
    }

    /**
     * 检查文件名是否合法
     *
     * @param path 相对路径
     * @return if valid
     */
    public static boolean isValidFileName(String path) {
        if (path != null) {
            for (int i = 0, len = path.length(); i < len; i++) {
                if (isInvalidPathChar(path.charAt(i))) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 检查文件名是否合法
     *
     * @param path    相对路径
     * @param message 错误信息
     * @throws IllegalArgumentException 参数验证无效
     */
    public static void validFileName(String path, String message) {
        if (!isValidFileName(path)) {
            throw new IllegalArgumentException(message);
        }
    }

    /**
     * 检查每一个字符
     * <p>
     * 包含空白字符和常见非法字符的检验
     *
     * @param c 字符
     * @return 是否有效
     */
    public static boolean isInvalidPathChar(char c) {
        return c < ' ' || reservedChars.indexOf(c) != -1;
    }
}
